﻿using M.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace M
{
    /// <summary>
    /// 过滤器的用法
    /// </summary>
    public class FilterContext : DbContext
    {
        public bool IgnoreDeleteFilter { get; set; } = false;

        public FilterContext(DbContextOptions<FilterContext> options) : base(options) 
        {
            Initialize();
           
        }

        protected override void OnConfiguring(DbContextOptionsBuilder options)
        {
            base.OnConfiguring(options);
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            //// 基本注册
            //builder.Entity<TestDelete>().HasQueryFilter(e => !e.IsDeleted);

            // 获取所有可注册的实体
            Assembly assembly = typeof(FilterContext).Assembly;
            //Assembly assembly = Assembly.GetExecutingAssembly();
            List<Type> entityTypes = assembly.GetTypes()
                .Where(t => t.IsSubclassOf(typeof(EntityBase)) && !t.IsAbstract)
                .ToList();

            // 生成MethodInfo， 用于给每个实体加载过滤器方法
            MethodInfo? configureFilters = typeof(FilterContext).GetMethod(
                nameof(ConfigureFilters),
                BindingFlags.Instance | BindingFlags.NonPublic
            );
            if (configureFilters == null) throw new ArgumentNullException(nameof(configureFilters));


            foreach (Type entityType in entityTypes)
            {
                // 注册实体
                builder.Entity(entityType);

                // 为每个实体注册筛选器
                configureFilters
                    .MakeGenericMethod(entityType)
                    .Invoke(this, new object[] { builder, entityType });
            }

            builder.TableMappingTo(
                e => { return true; }, // 校验是否需要处理名称
                e => { return e.ToLower(); },
                e => { return e.ToLower(); }
            );
        }

        #region 查询过滤器
        protected virtual void ConfigureFilters<TEntity>(ModelBuilder builder, Type entityType)
                where TEntity : class
        {
            Expression<Func<TEntity, bool>>? expression = null;

            if (typeof(ISoftDelete).IsAssignableFrom(entityType))
            {
                expression = e => IgnoreDeleteFilter || !EF.Property<bool>(e, "IsDeleted");
            }

            if (typeof(ITenant).IsAssignableFrom(entityType))
            {
                Expression<Func<TEntity, bool>> tenantExpression = e => EF.Property<int>(e, "TenantId") == 1;
                expression = expression == null ? tenantExpression : CombineExpressions(expression, tenantExpression);
            }

            if (expression == null) return;

            builder.Entity<TEntity>().HasQueryFilter(expression);
        }

        protected virtual Expression<Func<T, bool>> CombineExpressions<T>(Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
        {
            var parameter = Expression.Parameter(typeof(T));

            var leftVisitor = new ReplaceExpressionVisitor(expression1.Parameters[0], parameter);
            var left = leftVisitor.Visit(expression1.Body);

            var rightVisitor = new ReplaceExpressionVisitor(expression2.Parameters[0], parameter);
            var right = rightVisitor.Visit(expression2.Body);

            return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left, right), parameter);
        }

        class ReplaceExpressionVisitor : ExpressionVisitor
        {
            private readonly Expression _oldValue;
            private readonly Expression _newValue;

            public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
            {
                _oldValue = oldValue;
                _newValue = newValue;
            }

            public override Expression Visit(Expression? node)
            {
                if (node == _oldValue)
                {
                    return _newValue;
                }

                return base.Visit(node)!;
            }
        }

        #endregion

        #region 增删改追踪器
        protected virtual void Initialize()
        {
            ChangeTracker.Tracked += ChangeTracker_Tracked;
            ChangeTracker.StateChanged += ChangeTracker_StateChanged;
        }

        private void ChangeTracker_Tracked(object? sender, EntityTrackedEventArgs e)
        {
            TrackedEntityEntry(e.Entry);
        }

        private void ChangeTracker_StateChanged(object? sender, EntityStateChangedEventArgs e)
        {
            TrackedEntityEntry(e.Entry);
        }

        private void TrackedEntityEntry(EntityEntry entry)
        {
            switch (entry.State)
            {
                case EntityState.Added:
                    EntityStateAdded(entry);
                    break;
                case EntityState.Modified:
                    EntityStateModified(entry);
                    break;
                case EntityState.Deleted:
                    EntityStateDeleted(entry);
                    break;
            }
        }

        protected virtual void EntityStateAdded(EntityEntry entry)
        {
            if (entry.Entity is ICreatedInfo createdEntity)
            {
                createdEntity.CreatedTime = DateTime.Now;
            }

            if (entry.Entity is ITenant tenantEntity)
            {
                if (tenantEntity.TenantId is null)
                {
                    tenantEntity.TenantId = 99;
                }
                //tenantEntity.TenantId = 99;
            }
        }

        protected virtual void EntityStateModified(EntityEntry entry)
        {
            if (entry.Entity is IUpdatedInfo updatedEntity)
            {
                updatedEntity.UpdatedTime = DateTime.Now;
            }
        }

        protected virtual void EntityStateDeleted(EntityEntry entry)
        {
            // 如果没有实现软删接口则直接返回，正常硬删
            if (!(entry.Entity is ISoftDelete softDelete))
            {
                return;
            }

            // 如果实现了软删，又想要硬删
            if (IgnoreDeleteFilter)
            {
                return;
            }

            //// 从数据库重载实体，这样做有个问题，每删一个实体访问一次数据库，效率得不到保证
            //entry.Reload();

            // 先将状态置为修改（会触发修改的事件）
            entry.State = EntityState.Modified;

            // 修改的事件结束以后，回来：遍历实体的属性，除了 IsDeleted 属性外，全将状态设置为未修改
            // 这样生成的 SQL 语句，只会更新一个 IsDeleted 的字段
            IEnumerable<PropertyEntry> properties = entry.Properties;

            foreach (var property in properties)
            {
                string name = property.Metadata.Name;
                if (name == nameof(ISoftDelete.IsDeleted)) continue;
                if ((entry.Entity is IUpdatedInfo) && (name == nameof(IUpdatedInfo.UpdatedTime) || name == nameof(IUpdatedInfo.UpdatorId))) continue;

                property.IsModified = false;
            }

            softDelete.IsDeleted = true;


            #region 方案1：abp的软删方案

            //// 从数据库重载实体，这样做有个问题，每删一个实体访问一次数据库，效率得不到保证
            //entry.Reload();
            //softDelete.IsDeleted = true;

            #endregion


            #region 方案2：

            //// 先将状态置为修改（会触发修改的事件）
            //entry.State = EntityState.Modified;

            //// 修改的事件结束以后，回来：遍历实体的属性，除了 IsDeleted 属性外，全将状态设置为未修改
            //// 这样生成的 SQL 语句，只会更新一个 IsDeleted 的字段
            //IEnumerable<PropertyEntry> properties = entry.Properties;
            //foreach (var property in properties)
            //{
            //    if (property.Metadata.Name == nameof(ISoftDelete.IsDeleted)) continue;
            //    property.IsModified = false;
            //}
            //softDelete.IsDeleted = true;

            #endregion


            #region 方案3：

            //// 状态修改
            //entry.State = EntityState.Unchanged;

            //// 设置单个状态修改
            //entry.Property(nameof(ISoftDelete.IsDeleted)).IsModified = true;
            //softDelete.IsDeleted = true;

            #endregion
        }
        #endregion
    }
}
